<template>
  <div class="upload-container">
    <!-- 拖拽上传区域 -->
    <div
      class="upload-dragger"
      :class="{ 'is-dragover': isDragOver, 'is-disabled': disabled }"
      @click="handleClick"
      @dragover="handleDragOver"
      @dragleave="handleDragLeave"
      @drop="handleDrop"
    >
      <div class="upload-content">
        <el-icon class="upload-icon" :size="48">
          <UploadFilled />
        </el-icon>
        <div class="upload-text">
          <p class="upload-title">{{ dragText }}</p>
          <p class="upload-hint">{{ hintText }}</p>
        </div>
        <div v-if="showButton" class="upload-buttons">
          <el-button
            type="primary"
            :disabled="disabled"
            @click.stop="handleClick"
          >
            {{ buttonText }}
          </el-button>
          <el-button
            v-if="allowFolder"
            type="success"
            :disabled="disabled"
            @click.stop="handleFolderClick"
          >
            选择文件夹
          </el-button>
        </div>
      </div>
    </div>

    <!-- 文件列表 -->
    <div v-if="fileList.length > 0" class="upload-list">
      <div
        v-for="(file, index) in fileList"
        :key="file.uid"
        class="upload-item"
        :class="{ 'is-error': file.status === 'error' }"
      >
        <div class="file-info">
          <el-icon class="file-icon">
            <Folder v-if="file.isFolder" />
            <Document v-else />
          </el-icon>
          <div class="file-details">
            <div class="file-name" :title="file.name">
              <span v-if="file.isFolder" class="folder-name">{{ file.name }}</span>
              <span v-else>{{ file.name }}</span>
              <span v-if="file.isFolder && file.fileCount" class="file-count">
                ({{ file.fileCount }} 个文件)
              </span>
            </div>
            <div class="file-size">
              <span v-if="file.isFolder">{{ file.fileCount }} 个文件</span>
              <span v-else>{{ formatFileSize(file.size) }}</span>
            </div>
          </div>
        </div>

        <div class="file-actions">
          <!-- 进度条 -->
          <div v-if="file.status === 'uploading'" class="upload-progress">
            <el-progress
              :percentage="file.percentage"
              :stroke-width="4"
              :show-text="false"
            />
          </div>

          <!-- 操作按钮 -->
          <div v-else class="action-buttons">
            <el-button
              v-if="file.status === 'success' && showPreview"
              type="primary"
              size="small"
              text
              @click="handlePreview(file)"
            >
              预览
            </el-button>
            <el-button
              v-if="file.status === 'success' && showDownload"
              type="primary"
              size="small"
              text
              @click="handleDownload(file)"
            >
              下载
            </el-button>
            <el-button
              type="danger"
              size="small"
              text
              @click="handleRemove(file, index)"
            >
              删除
            </el-button>
          </div>
        </div>
      </div>
    </div>

    <!-- 图片预览对话框 -->
    <el-dialog
      v-model="previewVisible"
      title="图片预览"
      width="80%"
      :before-close="handlePreviewClose"
    >
      <div class="preview-container">
        <img
          v-if="previewFile"
          :src="previewFile.url"
          :alt="previewFile.name"
          class="preview-image"
        />
      </div>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { UploadFilled, Document, Folder } from '@element-plus/icons-vue';
import { formatFileSize } from '@/utils/helps/common';

/**
 * 文件类型定义
 */
interface UploadFile {
  uid: string;
  name: string;
  size: number;
  status: 'ready' | 'uploading' | 'success' | 'error';
  percentage: number;
  url?: string;
  raw?: File;
  response?: any;
  isFolder?: boolean; // 是否为文件夹
  fileCount?: number; // 文件夹内文件数量
  folderPath?: string; // 文件夹路径
  files?: UploadFile[]; // 文件夹内的文件列表
}

/**
 * 组件Props
 */
interface Props {
  modelValue?: UploadFile[];
  action?: string;
  headers?: Record<string, string>;
  data?: Record<string, any>;
  multiple?: boolean;
  accept?: string;
  maxSize?: number;
  maxCount?: number;
  disabled?: boolean;
  dragText?: string;
  hintText?: string;
  buttonText?: string;
  showButton?: boolean;
  showPreview?: boolean;
  showDownload?: boolean;
  autoUpload?: boolean;
  allowFolder?: boolean; // 是否允许上传文件夹
  folderMaxDepth?: number; // 文件夹最大深度
  folderMaxFiles?: number; // 文件夹内最大文件数
  beforeUpload?: (file: File) => boolean | Promise<boolean>;
  onSuccess?: (response: any, file: UploadFile) => void;
  onError?: (error: any, file: UploadFile) => void;
  onProgress?: (event: any, file: UploadFile) => void;
  onRemove?: (file: UploadFile) => void;
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: () => [],
  action: '',
  headers: () => ({}),
  data: () => ({}),
  multiple: false,
  accept: '*',
  maxSize: 10 * 1024 * 1024, // 10MB
  maxCount: 0,
  disabled: false,
  dragText: '点击或拖拽文件到此区域上传',
  hintText: '支持单个或批量上传',
  buttonText: '选择文件',
  showButton: true,
  showPreview: true,
  showDownload: true,
  autoUpload: true,
  allowFolder: false,
  folderMaxDepth: 5,
  folderMaxFiles: 100
});

/**
 * 组件Emits
 */
const emit = defineEmits<{
  'update:modelValue': [files: UploadFile[]];
  'change': [files: UploadFile[]];
  'success': [response: any, file: UploadFile];
  'error': [error: any, file: UploadFile];
  'progress': [event: any, file: UploadFile];
  'remove': [file: UploadFile];
}>();

/**
 * 响应式数据
 */
const fileList = ref<UploadFile[]>([]);
const isDragOver = ref(false);
const previewVisible = ref(false);
const previewFile = ref<UploadFile | null>(null);
const fileInput = ref<HTMLInputElement | null>(null);
const folderInput = ref<HTMLInputElement | null>(null);

/**
 * 计算属性
 */
const canUpload = computed(() => {
  if (props.disabled) return false;
  if (props.maxCount > 0 && fileList.value.length >= props.maxCount) return false;
  return true;
});

/**
 * 监听modelValue变化
 */
watch(
  () => props.modelValue,
  (newValue) => {
    fileList.value = [...newValue];
  },
  { immediate: true, deep: true }
);

/**
 * 监听fileList变化
 */
watch(
  fileList,
  (newValue) => {
    emit('update:modelValue', [...newValue]);
    emit('change', [...newValue]);
  },
  { deep: true }
);

/**
 * 生成唯一ID
 */
const generateUID = (): string => {
  return Date.now().toString(36) + Math.random().toString(36).substr(2);
};

/**
 * 验证文件
 */
const validateFile = (file: File): boolean => {
  // 检查文件大小
  if (file.size > props.maxSize) {
    ElMessage.error(`文件大小不能超过 ${formatFileSize(props.maxSize)}`);
    return false;
  }

  // 检查文件类型
  if (props.accept !== '*') {
    const acceptTypes = props.accept.split(',').map(type => type.trim());
    const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
    const mimeType = file.type;
    
    const isValidType = acceptTypes.some(type => {
      if (type.startsWith('.')) {
        return fileExtension === type.toLowerCase();
      }
      if (type.includes('/')) {
        return mimeType === type;
      }
      return false;
    });

    if (!isValidType) {
      ElMessage.error(`不支持的文件类型: ${file.name}`);
      return false;
    }
  }

  return true;
};

/**
 * 创建文件对象
 */
const createFileObject = (file: File): UploadFile => {
  return {
    uid: generateUID(),
    name: file.name,
    size: file.size,
    status: 'ready',
    percentage: 0,
    raw: file
  };
};

/**
 * 上传文件
 */
const uploadFile = async (file: UploadFile): Promise<void> => {
  if (!props.action) {
    ElMessage.error('未设置上传地址');
    return;
  }

  file.status = 'uploading';
  file.percentage = 0;

  const formData = new FormData();
  formData.append('file', file.raw!);

  // 添加额外数据
  Object.keys(props.data).forEach(key => {
    formData.append(key, props.data[key]);
  });

  try {
    const xhr = new XMLHttpRequest();

    // 上传进度
    xhr.upload.addEventListener('progress', (event) => {
      if (event.lengthComputable) {
        file.percentage = Math.round((event.loaded / event.total) * 100);
        emit('progress', event, file);
      }
    });

    // 上传完成
    xhr.addEventListener('load', () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        try {
          const response = JSON.parse(xhr.responseText);
          file.status = 'success';
          file.response = response;
          file.url = response.url || response.data?.url;
          
          emit('success', response, file);
          props.onSuccess?.(response, file);
        } catch (error) {
          file.status = 'error';
          emit('error', error, file);
          props.onError?.(error, file);
        }
      } else {
        file.status = 'error';
        const error = new Error(`上传失败: ${xhr.status}`);
        emit('error', error, file);
        props.onError?.(error, file);
      }
    });

    // 上传错误
    xhr.addEventListener('error', () => {
      file.status = 'error';
      const error = new Error('网络错误');
      emit('error', error, file);
      props.onError?.(error, file);
    });

    xhr.open('POST', props.action);
    
    // 设置请求头
    Object.keys(props.headers).forEach(key => {
      xhr.setRequestHeader(key, props.headers[key]);
    });

    xhr.send(formData);
  } catch (error) {
    file.status = 'error';
    emit('error', error, file);
    props.onError?.(error, file);
  }
};

/**
 * 处理文件选择
 */
const handleFiles = async (files: FileList | File[]): Promise<void> => {
  const fileArray = Array.from(files);
  
  // 检查文件数量限制
  if (props.maxCount > 0 && fileList.value.length + fileArray.length > props.maxCount) {
    ElMessage.error(`最多只能上传 ${props.maxCount} 个文件`);
    return;
  }

  for (const file of fileArray) {
    if (!validateFile(file)) continue;

    // 执行上传前钩子
    if (props.beforeUpload) {
      try {
        const result = await props.beforeUpload(file);
        if (!result) continue;
      } catch (error) {
        ElMessage.error('文件验证失败');
        continue;
      }
    }

    const fileObj = createFileObject(file);
    fileList.value.push(fileObj);

    // 自动上传
    if (props.autoUpload) {
      await uploadFile(fileObj);
    }
  }
};

/**
 * 点击上传区域
 */
const handleClick = (): void => {
  if (!canUpload.value) return;

  if (!fileInput.value) {
    fileInput.value = document.createElement('input');
    fileInput.value.type = 'file';
    fileInput.value.multiple = props.multiple;
    fileInput.value.accept = props.accept;
    fileInput.value.style.display = 'none';
    document.body.appendChild(fileInput.value);

    fileInput.value.addEventListener('change', (event) => {
      const target = event.target as HTMLInputElement;
      if (target.files) {
        handleFiles(target.files);
      }
      target.value = '';
    });
  }

  fileInput.value.click();
};

/**
 * 点击选择文件夹
 */
const handleFolderClick = (): void => {
  if (!canUpload.value || !props.allowFolder) return;

  if (!folderInput.value) {
    folderInput.value = document.createElement('input');
    folderInput.value.type = 'file';
    folderInput.value.webkitdirectory = true; // 启用文件夹选择
    folderInput.value.multiple = true;
    folderInput.value.style.display = 'none';
    document.body.appendChild(folderInput.value);

    folderInput.value.addEventListener('change', (event) => {
      const target = event.target as HTMLInputElement;
      if (target.files) {
        handleFolderFiles(target.files);
      }
      target.value = '';
    });
  }

  folderInput.value.click();
};

/**
 * 处理文件夹文件
 */
const handleFolderFiles = async (files: FileList): Promise<void> => {
  const fileArray = Array.from(files);
  
  if (fileArray.length === 0) return;

  // 检查文件夹内文件数量限制
  if (props.folderMaxFiles > 0 && fileArray.length > props.folderMaxFiles) {
    ElMessage.error(`文件夹内文件数量不能超过 ${props.folderMaxFiles} 个`);
    return;
  }

  // 检查文件夹深度
  const maxDepth = props.folderMaxDepth || 5;
  const folderPaths = new Set(fileArray.map(file => file.webkitRelativePath.split('/').length - 1));
  const maxFolderDepth = Math.max(...folderPaths);
  
  if (maxFolderDepth > maxDepth) {
    ElMessage.error(`文件夹深度不能超过 ${maxDepth} 层`);
    return;
  }

  // 按文件夹分组文件
  const folderMap = new Map<string, File[]>();
  
  fileArray.forEach(file => {
    const pathParts = file.webkitRelativePath.split('/');
    const folderName = pathParts[0];
    
    if (!folderMap.has(folderName)) {
      folderMap.set(folderName, []);
    }
    folderMap.get(folderName)!.push(file);
  });

  // 为每个文件夹创建上传项
  for (const [folderName, files] of folderMap) {
    const folderFile: UploadFile = {
      uid: generateUID(),
      name: folderName,
      size: files.reduce((sum, file) => sum + file.size, 0),
      status: 'ready',
      percentage: 0,
      isFolder: true,
      fileCount: files.length,
      folderPath: folderName,
      files: files.map(file => createFileObject(file))
    };

    // 验证文件夹内的文件
    let validFiles = files;
    if (props.beforeUpload) {
      validFiles = files.filter(file => {
        try {
          return props.beforeUpload!(file);
        } catch (error) {
          console.error('文件验证失败:', error);
          return false;
        }
      });
    }

    if (validFiles.length === 0) {
      ElMessage.warning(`文件夹 ${folderName} 中没有有效文件`);
      continue;
    }

    folderFile.files = validFiles.map(file => createFileObject(file));
    folderFile.fileCount = validFiles.length;
    folderFile.size = validFiles.reduce((sum, file) => sum + file.size, 0);

    fileList.value.push(folderFile);

    // 自动上传文件夹
    if (props.autoUpload) {
      await uploadFolder(folderFile);
    }
  }
};

/**
 * 上传文件夹
 */
const uploadFolder = async (folderFile: UploadFile): Promise<void> => {
  if (!folderFile.files || folderFile.files.length === 0) return;

  folderFile.status = 'uploading';
  folderFile.percentage = 0;

  try {
    let successCount = 0;
    const totalFiles = folderFile.files.length;

    for (let i = 0; i < folderFile.files.length; i++) {
      const file = folderFile.files[i];
      
      try {
        await uploadFile(file);
        successCount++;
      } catch (error) {
        console.error(`文件 ${file.name} 上传失败:`, error);
      }

      // 更新文件夹上传进度
      folderFile.percentage = Math.round((successCount / totalFiles) * 100);
    }

    if (successCount === totalFiles) {
      folderFile.status = 'success';
      ElMessage.success(`文件夹 ${folderFile.name} 上传成功！`);
    } else {
      folderFile.status = 'error';
      ElMessage.error(`文件夹 ${folderFile.name} 部分文件上传失败！`);
    }
  } catch (error) {
    folderFile.status = 'error';
    ElMessage.error(`文件夹 ${folderFile.name} 上传失败！`);
    console.error('文件夹上传失败:', error);
  }
};

/**
 * 拖拽相关事件
 */
const handleDragOver = (event: DragEvent): void => {
  event.preventDefault();
  if (canUpload.value) {
    isDragOver.value = true;
  }
};

const handleDragLeave = (event: DragEvent): void => {
  event.preventDefault();
  isDragOver.value = false;
};

const handleDrop = (event: DragEvent): void => {
  event.preventDefault();
  isDragOver.value = false;

  if (!canUpload.value) return;

  const files = event.dataTransfer?.files;
  if (files) {
    handleFiles(files);
  }
};

/**
 * 删除文件
 */
const handleRemove = async (file: UploadFile, index: number): Promise<void> => {
  try {
    await ElMessageBox.confirm('确定要删除这个文件吗？', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    });

    fileList.value.splice(index, 1);
    emit('remove', file);
    props.onRemove?.(file);
  } catch {
    // 用户取消删除
  }
};

/**
 * 预览文件
 */
const handlePreview = (file: UploadFile): void => {
  if (file.url) {
    previewFile.value = file;
    previewVisible.value = true;
  }
};

/**
 * 关闭预览
 */
const handlePreviewClose = (): void => {
  previewVisible.value = false;
  previewFile.value = null;
};

/**
 * 下载文件
 */
const handleDownload = (file: UploadFile): void => {
  if (file.url) {
    const link = document.createElement('a');
    link.href = file.url;
    link.download = file.name;
    link.style.display = 'none';
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  }
};

/**
 * 手动上传所有文件
 */
const submit = (): void => {
  const readyFiles = fileList.value.filter(file => file.status === 'ready');
  readyFiles.forEach(file => uploadFile(file));
};

/**
 * 清空文件列表
 */
const clearFiles = (): void => {
  fileList.value = [];
};

/**
 * 暴露方法
 */
defineExpose({
  submit,
  clearFiles
});
</script>

<style scoped>
.upload-container {
  width: 100%;
}

.upload-dragger {
  border: 2px dashed #d9d9d9;
  border-radius: 6px;
  width: 100%;
  height: 180px;
  text-align: center;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: border-color 0.3s;
}

.upload-dragger:hover {
  border-color: #409eff;
}

.upload-dragger.is-dragover {
  border-color: #409eff;
  background-color: rgba(64, 158, 255, 0.06);
}

.upload-dragger.is-disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.upload-content {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  padding: 20px;
}

.upload-icon {
  color: #c0c4cc;
  margin-bottom: 16px;
}

.upload-text {
  margin-bottom: 16px;
}

.upload-title {
  color: #606266;
  font-size: 14px;
  margin: 0 0 8px 0;
}

.upload-hint {
  color: #909399;
  font-size: 12px;
  margin: 0;
}

.upload-list {
  margin-top: 20px;
}

.upload-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  margin-bottom: 8px;
  background-color: #fafafa;
}

.upload-item.is-error {
  border-color: #f56c6c;
  background-color: #fef0f0;
}

.file-info {
  display: flex;
  align-items: center;
  flex: 1;
}

.file-icon {
  color: #409eff;
  margin-right: 12px;
  font-size: 20px;
}

.file-details {
  flex: 1;
}

.file-name {
  color: #303133;
  font-size: 14px;
  margin-bottom: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-size {
  color: #909399;
  font-size: 12px;
}

.folder-name {
  color: #409eff;
  font-weight: 500;
}

.file-count {
  color: #909399;
  font-size: 11px;
  margin-left: 4px;
}

.file-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.upload-progress {
  width: 200px;
}

.action-buttons {
  display: flex;
  gap: 8px;
}

.preview-container {
  text-align: center;
}

.preview-image {
  max-width: 100%;
  max-height: 70vh;
  object-fit: contain;
}

.upload-buttons {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
}
</style>
